/*
 *    Copyright 2009-2022 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder.xml;

import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.CacheRefResolver;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.ResultMapResolver;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

/**
 * @author Clinton Begin
 * @author Kazuki Shimizu
 */
public class XMLMapperBuilder extends BaseBuilder {

  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
    this(reader, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
    this(inputStream, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

  /**
   * 解析映射文件的路口
   */
  public void parse() {
    // 1. 判断是否已经加载过该映射文件
    if (!configuration.isResourceLoaded(resource)) {
      // 2. 处理＜mapper ＞节点
      configurationElement(parser.evalNode("/mapper"));
      // 3. 将 resource 添加到 Configuration.loadedResources 集合中保存吗，它是 HashSet<String>
      // 类型的集合，其中记录了已经加载过的映射文件
      configuration.addLoadedResource(resource);
      // 4. 注册 mapper 接口
      bindMapperForNamespace();
    }

    // 5. 处理 configurationElement （）方法 解析失败的＜resultMap 节点
    parsePendingResultMaps();
    // 6. 处理 configurationElement （）方法中解析失败的＜cache-ref ＞节点
    parsePendingCacheRefs();
    // 7. 处理 configurationElement （）方法中解析失败的 SQL 语句节点
    parsePendingStatements();
  }

  public XNode getSqlFragment(String refid) {
    return sqlFragments.get(refid);
  }

  /**
   * 解析 mappers 节点
   */
  private void configurationElement(XNode context) {
    try {
      // 1. 获取＜mapper>节点的namespace 属性
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        // 2. 如采 namespace 属性为空，则抛出异常
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // 3. 设置 MapperBuilderAssistant 的 currentNamespace 字段，记录当前命名空间
      builderAssistant.setCurrentNamespace(namespace);
      // 4. ／解析＜ cache-ref> 节点
      cacheRefElement(context.evalNode("cache-ref"));
      // 5. 解析＜ cache ＞节点
      cacheElement(context.evalNode("cache"));
      // 6. 解析＜parameterMap ＞节点（该节点已废弃，不再推荐使用，不做详细介绍）
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 7. 解析＜ resultMap ＞节点
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 8. 解析＜ sql ＞节点
      sqlElement(context.evalNodes("/mapper/sql"));
      // 9. 解析＜ select ＞、＜ insert ＞、＜ update ＞、＜ delete ＞等 SQL 节点
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

  private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
      Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolve();
          iter.remove();
        } catch (IncompleteElementException e) {
          // ResultMap is still missing a resource...
        }
      }
    }
  }

  private void parsePendingCacheRefs() {
    Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
    synchronized (incompleteCacheRefs) {
      Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolveCacheRef();
          iter.remove();
        } catch (IncompleteElementException e) {
          // Cache ref is still missing a resource...
        }
      }
    }
  }

  private void parsePendingStatements() {
    // 1. ／获取 Configuration incompleteStatements 集合
    Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
    // 2. ／加锁同步
    synchronized (incompleteStatements) {
      // 3. 遍历 incompleteStatements 集合
      Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
      while (iter.hasNext()) {
        try {
          // 4. 重新解析 SQL 语句节点
          iter.next().parseStatementNode();
          // 5. 移除 XMLStatementBuilder 对象
          iter.remove();
        } catch (IncompleteElementException e) {
          // 6. ／如采依然元法 析，则 忽略该节点
          // Statement is still missing a resource...
        }
      }
    }
  }

  /**
   * 通过前面对＜cache＞节点解析过程的介绍我们知道， XMLMapperBuilder.cacheElement（）方法
   * 会为每个 namespace 创建 个对应的 Cache 对象，井在 Configuration.caches 集合中记
   * namespace Cache 对象之间的对应关系。如果我们希望多个 names pace 共用同一个二级缓存，
   * 即同一个 Cache 对象，则可以使用＜cache-ref>节点进行配置。
   * @param context
   */
  private void cacheRefElement(XNode context) {
    if (context != null) {
      // 1. 将当前 Mapper 配置文件的 namespace 与被引用的 Cache 所在的 namespace 之间的对应关系，
      // 2. 记录到 Configuration.cacheRefMap 集合中
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      // 3. 创建 CacheRefResolver 对象
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        // 4. 解析 Cache 引用，该过程主要是设置 MapperBuilderAssistant 中的 currentCache 和 unresolvedCacheRef 字段
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        // 5. 如果析过程出现异常，则添加到 Configuration.incompleteCacheRefs 集合， 稍后再解析
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

  /**
   * 解析<cache>节点
   * MyBatis 有非常强大的二级缓存功能， 功能可以非常方便地进行配置， MyBatis 默认情
   * 况下没有开启二级缓存，如果要为某命名空间开启 二级缓存功能，则需要在相应映射配置文件
   * 中添加＜ cache＞节点，还可以通过配置＜cache＞节点 的相关属性，为二级缓存配置新的特性 （本
   * 质上就是添加相应的装饰器〉。
   *
   */
  private void cacheElement(XNode context) {
    if (context != null) {
      // 1. 获取＜ cache ＞节点的 type ）属性，默认位是 PERPETUAL
      String type = context.getStringAttribute("type", "PERPETUAL");
      // 2. 查找 type 属性对应的 Cache 实现， TypeAliasRegistry 的实现前面介绍过了，不再赘述
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      // 3. 获取＜ cache 节点的 eviction 属性，默认位是 LRU
      String eviction = context.getStringAttribute("eviction", "LRU");
      // 4. 解析 eviction 属，性指定的 Cache 装饰器类型
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      // 5. 获取＜ cache ＞节点的 flushInterval 属，性，默认值是 null
      Long flushInterval = context.getLongAttribute("flushInterval");
      // 6. 获取＜ cache ＞节点的 size 属性，默认位是 null
      Integer size = context.getIntAttribute("size");
      // 7. 获取＜ cache ＞节点的 readOnly 属性，默认值是 false
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      // 8. 获取＜ cache ＞节点的 blocking 属性 默认位是 false
      boolean blocking = context.getBooleanAttribute("blocking", false);
      // 9. 获取＜cache ＞节点下的子节点，将用于初始化二级缓存
      Properties props = context.getChildrenAsProperties();
      // 10. 通过 MapperBuilderAssistant 创建 Cache 对象，并添加到 Configuration.caches 集合中保存
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

  /**
   * 在 MyBatis 官方文档 中明确标记＜parameterMap 节点己废弃了，在将来的
   * 版本中可能会被移除，所以不建议大家使用，这里也不做详细介绍。
   */
  private void parameterMapElement(List<XNode> list) {
    for (XNode parameterMapNode : list) {
      String id = parameterMapNode.getStringAttribute("id");
      String type = parameterMapNode.getStringAttribute("type");
      Class<?> parameterClass = resolveClass(type);
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
      List<ParameterMapping> parameterMappings = new ArrayList<>();
      for (XNode parameterNode : parameterNodes) {
        String property = parameterNode.getStringAttribute("property");
        String javaType = parameterNode.getStringAttribute("javaType");
        String jdbcType = parameterNode.getStringAttribute("jdbcType");
        String resultMap = parameterNode.getStringAttribute("resultMap");
        String mode = parameterNode.getStringAttribute("mode");
        String typeHandler = parameterNode.getStringAttribute("typeHandler");
        Integer numericScale = parameterNode.getIntAttribute("numericScale");
        ParameterMode modeEnum = resolveParameterMode(mode);
        Class<?> javaTypeClass = resolveClass(javaType);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
        ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
        parameterMappings.add(parameterMapping);
      }
      builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
  }

  /**
   * 解决的问题：
   * 1、JDBC 编程中，为了将结果集中的数据映射成对象，我们需要自
   * 己写代码从结果集中获取数据，然后封装成对应的对象并设置对象之间的关系，而这些都是大
   * 量的重复性代码。为了减少这些重复的代码， MyBatis 使用 resultMap＞节点定义了结果集与结
   * 果对象 (JavaBean 对象〉之间的映射规则，＜resultMap＞节点可以满足绝大部分的映射需求，从
   * 而减少开发人员的重复性劳动，提高开发效率。
   * @param list
   */
  private void resultMapElements(List<XNode> list) {
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

  private ResultMap resultMapElement(XNode resultMapNode) {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
  }

  /**
   * 通过 resultMapElements（） 方法解析映射配置文件中的全部
   * resultMap＞节点 该方法会循环调用 resultMapElement（） 方法处理每个＜resultMap＞节点
   */
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 1。 获取＜ resultMap＞节点 type 属性，表示结果集将被映 type 指定类型的对象，注意其默认值
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    // 2.  解析 type 类型
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    // 3. 处理＜ resultMap 的子节点
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        // 4. ／处理＜ constructor 节点
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        // 5. 处理＜ discriminator ＞节点
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        // 6. 处理 id ＞、＜ result ＞、 association ＞、＜ collection ＞等节点
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          // 7. 如果是＜id＞节点，则向 flags 集合中添加 ResultFlag ID
          flags.add(ResultFlag.ID);
        }
        // 8. 创建 ResultMapping 对象，并添加 resultMappings 集合中保存
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    // 9. 获取＜ resultMap ＞的 id 属性，默认值会拼装所有父节点的 id 或 value 或 Property 属性值，感兴
    // 趣的读者请参考 XNode.getValueBasedIdentifier() 方法的实现，这里不详细介绍
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    // 10. 获取＜ resultMap 节点的 extends 属性，该属性指定了该＜resultMap＞节点 继承关系
    String extend = resultMapNode.getStringAttribute("extends");
    // 11. 读取＜ resultMap ＞节点的 autoMapping 属性 将该属性设置为 true ，则启动自动映射功能，
    // 即自动查找与列名同名的属性名，并调用 setter 方法 而设置为 false 后，则需要
    // 在 resultMap 节点内明确注明映射关系才会调用对应 setter 方法
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      // 12. 创建 ResultMap 对象，并添加到 Configuration.resultMaps 集合中，该集合是 StrictMap 类型
      return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

  protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingType) {
    if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
      String property = resultMapNode.getStringAttribute("property");
      if (property != null && enclosingType != null) {
        MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
        return metaResultType.getSetterType(property);
      }
    } else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
      return enclosingType;
    }
    return null;
  }

  /**
   * 涉及 constructor＞节点的解析，该过程由 XMLMapperBuilder.processConstructorElement（）方法完成
   */
  private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
    // 1. 获取＜constructor ＞节点的子节点
    List<XNode> argChildren = resultChild.getChildren();
    for (XNode argChild : argChildren) {
      List<ResultFlag> flags = new ArrayList<>();
      // 2. 添加 CONSTRUCTOR 标志
      flags.add(ResultFlag.CONSTRUCTOR);
      // 3. 对于＜ idArg ＞节点，添加工 标志
      if ("idArg".equals(argChild.getName())) {
        flags.add(ResultFlag.ID);
      }

      // 4. 创建 ResultMapping 对象，并添加到 resultMappings 集合中
      resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
  }

  /**
   * 涉及＜discriminator＞节点的解 析
   */
  private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) {
    // 1. ． ．． 获取 column \Java \Type \jdbcType \typeHandler 属性
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    Map<String, String> discriminatorMap = new HashMap<>();

    // 2. 处理＜discriminator ＞节点的子节点
    for (XNode caseChild : context.getChildren()) {
      String value = caseChild.getStringAttribute("value");
      // 3. 调用 processNestedResultMappings 方法创建嵌套 ResultMap 对象
      String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings, resultType));
      // 4. 记录该列值与对应选择的 ResultMapId
      discriminatorMap.put(value, resultMap);
    }

    // 5. 创建 Discriminator 对象
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
  }

  /**
   * 1、在映射配置文件中，可以使用＜sq ＞节点定义可重用的 SQL 语句片段。当需要重用＜sql＞节
   * 点中定义的 SQL 语句片段时，只需要使用＜include 节点引入相应的片段即可，这样，在编写
   * SQL 语句以及维护这些 SQL 语句时 ，都比较方便。
   *
   * 2、负责解析映射配置文件中定义的全部＜sql＞节点
   */
  private void sqlElement(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    // 1. 遥历＜ sql ＞节点
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      // 2. 获取 id 属性
      String id = context.getStringAttribute("id");
      // 3. 为 id 添加命名空间
      id = builderAssistant.applyCurrentNamespace(id, false);
      // 4. 检测＜ sql ＞的 database Id 与当前 Configuration 中记录的 database Id 是否一致
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        // 5. 记录到 XMLMapperBuilder sqlFragments ( Map<String , XNode ＞类型）中保存，在
        //XMLMapperBuilder 的构造函数中，可以看到该字段指向了 Configuration.sqlFragments 集合
        sqlFragments.put(id, context);
      }
    }
  }

  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
      return requiredDatabaseId.equals(databaseId);
    }
    if (databaseId != null) {
      return false;
    }
    if (!this.sqlFragments.containsKey(id)) {
      return true;
    }
    // skip this fragment if there is a previous one with a not null databaseId
    XNode context = this.sqlFragments.get(id);
    return context.getStringAttribute("databaseId") == null;
  }

  /**
   * 为 result>节点创建对应的 ResultMapping 对象，
   */
  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
    String property;
    // 1. 获取该节点的 name 或 property 的属性位
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      property = context.getStringAttribute("property");
    }

    // 2. ... 获取 column\ JavaType\ jdbcType\ select 等属性位，
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    // 如果未指定＜association ＞节点的 resultMap 属性，则是匿名的嵌套映射，需要通过
    // processNestedResultMappings ｛｝方法解析该匿名的嵌套映射，在后面分析 collection ＞节点时
    // 还会涉及匿名嵌套映射的解析过程
    String nestedResultMap = context.getStringAttribute("resultMap", () ->
        processNestedResultMappings(context, Collections.emptyList(), resultType));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    // 3. 解析 javaType typeHandler dbcType
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // 4. 创建 ResultMapping 对象
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

  /**
   * 处理嵌套的 resultMap
   * 只会处理 association ＞、＜ collection ＞和＜ case ＞三种节点
   *
   */
  private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) {
    // 1. 只会处理 association ＞、＜ collection ＞和＜ case ＞三种节点，并且指定 select 属性之后，不会生成嵌套的 ResultMap 对象
    if (Arrays.asList("association", "collection", "case").contains(context.getName())
        && context.getStringAttribute("select") == null) {
      validateCollection(context, enclosingType);
      // 2. 创建 ResultMap 对象，并添加 Configuration.resultMaps 集合中 注意，本例中
      // <association ＞节点没有 id ，其 id 由 XNode.getValueBasedidentifier （）方法生成，
      // 本例中 id 为” mapper_resultMap[detailedBlogResultMap]_association[author] ”
      // 另外，本例中的 association ＞节点指定了 resultMap 属性，而非匿名的嵌套映射，所以该
      // ResultMap 对象中的 resultMappings 集合为空
      // 来分析对＜collection＞节点的解析过程，在上述示例中，解析其中的 collection＞节点时，
      // 除了上述已经分 析过的步骤之外，还 需要特别关注 XMLMapperBuilder. processNestedResultMappings（）方法对其中 匿名 的嵌套映射的处理。
      // 该方法会调用 resultMapElement（）方法解析
      // collection＞节点 的子节点，创建相应的 ResultMap 象并添加到 Configuration.resultMaps 集合
      // 中保存，其具体实现上面己经介绍过了。
      ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
      return resultMap.getId();
    }
    return null;
  }

  protected void validateCollection(XNode context, Class<?> enclosingType) {
    if ("collection".equals(context.getName()) && context.getStringAttribute("resultMap") == null
        && context.getStringAttribute("javaType") == null) {
      MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
      String property = context.getStringAttribute("property");
      if (!metaResultType.hasSetter(property)) {
        throw new BuilderException(
            "Ambiguous collection type for property '" + property + "'. You must specify 'javaType' or 'resultMap'.");
      }
    }
  }

  /**
   * ，完成了映射配置文件与对应 Mapper
   * 口的绑定
   */
  private void bindMapperForNamespace() {
    // 1. 获取映射配 文件的命名空间
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        // 2. 解析命名空间对应的类型
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      // 3. 是否已经加载了 boundType 接口
      if (boundType != null && !configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        // 4. 追加 name space 缀，并添加到 Configuration.loadedResource 合中保存
        configuration.addLoadedResource("namespace:" + namespace);
        // 5. 调 apperRegistry .a ddMapper （）方法，注册 boundType 接口
        configuration.addMapper(boundType);
      }
    }
  }

}
